using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Data.Common;
using System.Reflection;

namespace Churchtoolkit.DataAccess.Common
{
    public abstract class ReadOnlyRepositoryBase<TEntity, TEnum> : IRepository
        where TEntity : IEntity, new()
        where TEnum : struct
    {
        #region [ Fields ]
        protected DbProviderFactory _connectionType;
        private readonly string _connectionstringName;
        #endregion

        #region [ Constructor ]
        protected ReadOnlyRepositoryBase(string connectionStringName)
        {
            _connectionstringName = connectionStringName;
            _connectionType =
                DbProviderFactories.GetFactory(
                    ConfigurationManager.ConnectionStrings[_connectionstringName].ProviderName);
        }
        #endregion

        #region [ Abstract properties ]
        protected abstract string SelectProc { get; }
        #endregion

        #region [ Find ]
        public EntityResult<TEntity> Find(Action<TEntity> expression, TEnum? orderByColumn, int? startIndex,
                                          int? length)
        {
            using (var connection = CreateConnection())
            {
                var returnedValues = new EntityResult<TEntity>();
                var entities = new List<TEntity>();
                var entity = new TEntity();
                var command = connection.CreateCommand();
                var changedProperties = new Dictionary<string, object>();
                //------------------------------------------------------------------------------------------------------
                if (expression != null)
                {
                    ((INotifyPropertyChanged)entity).PropertyChanged +=
                        (sender, e) =>
                        changedProperties.Add(e.PropertyName, entity.GetType().GetPropertyValue(e.PropertyName, entity));
                    expression(entity);
                }
                //------------------------------------------------------------------------------------------------------
                command.CommandText = SelectProc;
                command.CommandType = CommandType.StoredProcedure;
                command.Parameters.AddRange(GetSelectParameters(changedProperties, orderByColumn, startIndex, length));
                //------------------------------------------------------------------------------------------------------
                DbParameter rowsCount = command.CreateParameter();
                rowsCount.Direction = ParameterDirection.ReturnValue;
                rowsCount.ParameterName = "RowsCount";
                command.Parameters.Add(rowsCount);
                DbDataReader reader;
                //------------------------------------------------------------------------------------------------------
                try
                {
                    connection.Open();
                    reader = command.ExecuteReader();
                    while (reader.Read())
                    {
                        entities.Add(FromReader(reader));
                    }
                }
                finally
                {
                    connection.Close();
                }
                returnedValues.Rows = entities;
                returnedValues.RowsCount = (int)rowsCount.Value;
                return returnedValues;
            }
        }

        public EntityResult<TEntity> Find(Action<TEntity> expression, TEnum orderByColumn)
        {
            return Find(expression, orderByColumn, null, null);
        }

        public EntityResult<TEntity> Find(Action<TEntity> expression)
        {
            return Find(expression, null, null, null);
        }

        public EntityResult<TEntity> FindAll()
        {
            return Find(null, null, null, null);
        }
        public EntityResult<TEntity> FindAll(TEnum orderby)
        {
            return Find(null, orderby, null, null);
        }
        public EntityResult<TEntity> FindAll(TEnum orderby, int startIndex, int length)
        {
            return Find(null, orderby, startIndex, length);
        }




        #endregion

        #region [ helper ]
        protected DbConnection CreateConnection()
        {
            DbConnection connection = _connectionType.CreateConnection();
            connection.ConnectionString = ConfigurationManager.ConnectionStrings[_connectionstringName].ConnectionString;
            return connection;
        }
        protected DbParameter[] GetSelectParameters(Dictionary<string, object> properties, TEnum? orderBy,
                                                    int? startIndex, int? length)
        {
            List<DbParameter> parameters = new List<DbParameter>();
            DbParameter tempParameter;
            foreach (var property in properties)
            {
                tempParameter = _connectionType.CreateParameter();
                tempParameter.ParameterName = "@" + property.Key;
                tempParameter.Value = property.Value;
                parameters.Add(tempParameter);
            }

            if (orderBy != null)
            {
                tempParameter = _connectionType.CreateParameter();
                tempParameter.ParameterName = "@OrderColume";
                tempParameter.Value = orderBy.ToString();
                parameters.Add(tempParameter);
            }

            if (startIndex != null)
            {
                tempParameter = _connectionType.CreateParameter();
                tempParameter.ParameterName = "@StartIndex";
                tempParameter.Value = startIndex.ToString();
                parameters.Add(tempParameter);
            }

            if (length != null)
            {
                tempParameter = _connectionType.CreateParameter();
                tempParameter.ParameterName = "@lenght";
                tempParameter.Value = length.ToString();
                parameters.Add(tempParameter);
            }
            return parameters.ToArray();
        }
        protected TEntity FromReader(DbDataReader reader)
        {
            var obj = new TEntity();
            PropertyInfo[] properties = obj.GetType().GetProperties();

            foreach (PropertyInfo property in properties)
            {
                if (ContainsColumn(reader, property.Name))
                {
                    switch (property.PropertyType.ToString())
                    {
                        case "System.String":
                            {
                                property.SetValue(obj, TypeConverter.ToString(reader[property.Name]), null);
                                break;
                            }

                        case "System.Int32":
                            {
                                property.SetValue(obj, TypeConverter.ToInt32(reader[property.Name]), null);
                                break;
                            }

                        case "System.Boolean":
                            {
                                property.SetValue(obj, TypeConverter.ToBoolean(reader[property.Name]), null);
                                break;
                            }

                        case "System.Decimal":
                            {
                                property.SetValue(obj, TypeConverter.ToDecimal(reader[property.Name]), null);
                                break;
                            }

                        case "System.DateTime":
                            {
                                property.SetValue(obj, TypeConverter.ToDateTime(reader[property.Name]), null);
                                break;
                            }
                        case "System.Byte":
                            {
                                property.SetValue(obj, TypeConverter.ToByte(reader[property.Name]), null);
                                break;
                            }

                        case "System.Nullable`1[System.Int32]":
                            {
                                property.SetValue(obj, TypeConverter.ToNullableInt32(reader[property.Name]), null);
                                break;
                            }

                        case "System.Nullable`1[System.DateTime]":
                            {
                                property.SetValue(obj, TypeConverter.ToNullableDateTime(reader[property.Name]), null);
                                break;
                            }

                        case "System.Nullable`1[System.Boolean]":
                            {
                                property.SetValue(obj, TypeConverter.ToNullableBoolean(reader[property.Name]), null);
                                break;
                            }

                        case "System.Nullable`1[System.Decimal]":
                            {
                                property.SetValue(obj, TypeConverter.ToNullableDecimal(reader[property.Name]), null);
                                break;
                            }

                        case "System.Nullable`1[System.Int64]":
                            {
                                property.SetValue(obj, TypeConverter.ToNullableInt64(reader[property.Name]), null);
                                break;
                            }

                        default:
                            {
                                break;
                            }
                    }
                }
            }
            return obj;
        }
        private static bool ContainsColumn(DbDataReader data, string column)
        {
            bool found = false;

            for (int i = 0; i < data.FieldCount; i++)
            {
                string name = data.GetName(i);
                if (name.ToLower() == column.ToLower())
                {
                    found = true;
                }
            }
            return found;
        }
        protected void Execute(DbCommand command, bool openConnection)
        {
            command.CommandType = CommandType.StoredProcedure;
            try
            {
                if (openConnection)
                    command.Connection.Open();
                command.ExecuteNonQuery();
            }
            finally
            {
                if (openConnection)
                    command.Connection.Close();
            }
        }
        #endregion
    }
}
